//https://operating-system-in-1000-lines.vercel.app/zh/05-hello-world
#include "kernel.h"

typedef unsigned char uint8_t;
typedef unsigned int uint32_t;
typedef uint32_t size_t;

extern char __bss[], __bss_end[], __stack_top[];

#define PROCS_MAX 8       // 最大进程数量

#define PROC_UNUSED   0   // 未使用的进程控制结构
#define PROC_RUNNABLE 1   // 可运行的进程

struct process *current_proc; // 当前运行的进程
struct process *idle_proc;    // 空闲进程
void yield(void);

void fs_init(void);

void delay(void) {
    for (int i = 0; i < 30000000; i++)
        __asm__ __volatile__("nop"); // 什么都不做
}


//内存分配（最简单的分配方式：按页分配）
extern char __free_ram[], __free_ram_end[];
paddr_t alloc_pages(uint32_t n) {
    static paddr_t next_paddr = (paddr_t) __free_ram;
    paddr_t paddr = next_paddr;
    next_paddr += n * PAGE_SIZE;

    if (next_paddr > (paddr_t) __free_ram_end)
        PANIC("out of memory");

    memset((void *) paddr, 0, n * PAGE_SIZE);
    return paddr;
}


/// @brief 内存映射，这里虚拟地址=物理地址，全映射
/// @param table1 动态申请的空间（第一个为__free_ram的位置）
/// @param vaddr  虚拟地址
/// @param paddr  物理地址
/// @param flags 设置内存的属性 PAGE_R | PAGE_W | PAGE_X
void map_page(uint32_t *table1, uint32_t vaddr, paddr_t paddr, uint32_t flags) {
    //检查地址否以页大小（PAGE_SIZE）对齐
    if (!is_aligned(vaddr, PAGE_SIZE))
        PANIC("unaligned vaddr %x", vaddr);

    if (!is_aligned(paddr, PAGE_SIZE))
        PANIC("unaligned paddr %x", paddr);

    /* 虚拟地址分布为 10bit+10bit+12bit 位结构，参考 https://riscv-sv32-virtual-address.vercel.app/
        vpn1: 31-22   vpn0: 21-12   offset:11-0
       操作系统使用的页表项格式通常规定：
       低 10 位（位 0 到位 9）用来存储各种标志位（例如有效位、读写权限等）
       高位（从位 10 开始）用来存储物理页号（一级/二级页号）
    */
    uint32_t vpn1 = (vaddr >> 22) & 0x3ff; //计算一级索引
    if ((table1[vpn1] & PAGE_V) == 0) {
        // 创建不存在的二级页表。
        uint32_t pt_paddr = alloc_pages(1);
        //(pt_paddr / PAGE_SIZE)计算页号
        table1[vpn1] = ((pt_paddr / PAGE_SIZE) << 10) | PAGE_V; 
    }

    // 设置二级页表项以映射物理页面。
    //具体映射规则：https://riscv-sv32-virtual-address.vercel.app/
    uint32_t vpn0 = (vaddr >> 12) & 0x3ff; //计算二级索引

    //逆运算，得到二级页表的页表项地址
    uint32_t *table0 = (uint32_t *) ((table1[vpn1] >> 10) * PAGE_SIZE);

    //二级页表项下存储真正的物理地址
    // flags | PAGE_V为偏移信息
    // paddr / PAGE_SIZE) 为页号
    table0[vpn0] = ((paddr / PAGE_SIZE) << 10) | flags | PAGE_V;
}


//这个函数设计用于按照 SBI 规范调用 OpenSBI
/*
第 3 章 二进制编码

所有 SBI 函数共享一个统一的二进制编码，这便于混合使用 SBI 扩展。SBI 规范遵循以下调用约定：

使用 ECALL 作为管理模式和 SEE 之间的控制转移指令。
a7 编码 SBI 扩展 ID（EID）
对于在 SBI v0.2 中或之后定义的任何 SBI 扩展，a6 编码给定扩展 ID 的 SBI 功能 ID（FID）
除了 a0 和 a1 外，所有寄存器必须在被调用方的 SBI 调用过程中保持不变。
SBI 函数必须在 a0 和 a1 中返回一对值，其中 a0 返回错误代码。这类似于返回 C 结构体

struct sbiret {
    long error;
    long value;
};
*/
struct sbiret sbi_call(long arg0, long arg1, long arg2, long arg3, long arg4,
                       long arg5, long fid, long eid) {
    register long a0 __asm__("a0") = arg0;
    register long a1 __asm__("a1") = arg1;
    register long a2 __asm__("a2") = arg2;
    register long a3 __asm__("a3") = arg3;
    register long a4 __asm__("a4") = arg4;
    register long a5 __asm__("a5") = arg5;
    register long a6 __asm__("a6") = fid;
    register long a7 __asm__("a7") = eid;

    __asm__ __volatile__("ecall"
                         : "=r"(a0), "=r"(a1)
                         : "r"(a0), "r"(a1), "r"(a2), "r"(a3), "r"(a4), "r"(a5),
                           "r"(a6), "r"(a7)
                         : "memory");
    return (struct sbiret){.error = a0, .value = a1};
}


/*
TIP

putchar() 的生命周期：

当调用 SBI 时，字符将按以下方式显示：

内核执行 ecall 指令。CPU 跳转到 M 模式陷阱处理程序（mtvec 寄存器），这是由 OpenSBI 在启动期间设置的。
保存寄存器后，调用用 C 编写的陷阱处理程序。
根据 eid，调用相应的 SBI 处理函数。
设备驱动程序（用于 8250 UART(Wikipedia)）将字符发送到 QEMU。
QEMU 的 8250 UART 模拟实现接收字符并将其发送到标准输出。
终端模拟器显示字符。
也就是说，调用 Console Putchar 函数根本不是魔法 - 它只是使用了在 OpenSBI 中实现的设备驱动程序！
*/
void putchar(char ch) {
    sbi_call(ch, 0, 0, 0, 0, 0, 0, 1 /* Console Putchar */);
}


long getchar(void) {
    struct sbiret ret = sbi_call(0, 0, 0, 0, 0, 0, 0, 2);
    return ret.error;
}


//处理用户层的系统调用
void handle_syscall(struct trap_frame *f) {
    //它通过检查 a3 寄存器的值来确定系统调用的类型(这个需跟用户层约定)
    switch (f->a3) {
        case SYS_PUTCHAR:
            putchar(f->a0); //调用内核的SBI
            break;
        case SYS_GETCHAR:
            while (1) {
                long ch = getchar();
                if (ch >= 0) {
                    f->a0 = ch;
                    break;
                }

                yield();
            }
        break;
        case SYS_EXIT: //程序退出，则进行调度
            printf("process %d exited\n", current_proc->pid);
            current_proc->state = PROC_EXITED; //调度器只会执行处于 PROC_RUNNABLE 状态的进程，所以它永远不会返回到这个进程
            yield();
            PANIC("unreachable");
            case SYS_READFILE:
            case SYS_WRITEFILE: {
                const char *filename = (const char *) f->a0;
                char *buf = (char *) f->a1;
                int len = f->a2;
                extern struct file *fs_lookup(const char *filename);
                struct file *file = fs_lookup(filename);
                if (!file) {
                    printf("file not found: %s\n", filename);
                    f->a0 = -1;
                    break;
                }
    
                if (len > (int) sizeof(file->data))
                    len = file->size;
    
                if (f->a3 == SYS_WRITEFILE) {
                    memcpy(file->data, buf, len);
                    file->size = len;
                    extern void fs_flush(void);
                    fs_flush();
                } else {
                    memcpy(buf, file->data, len);
                }
    
                f->a0 = len;
                break;
            }
        default:
            PANIC("unexpected syscall a3=%x\n", f->a3);
    }
}


//具体的异常处理函数
void handle_trap(struct trap_frame *f) {
    uint32_t scause = READ_CSR(scause);
    uint32_t stval = READ_CSR(stval);
    uint32_t user_pc = READ_CSR(sepc);

    //可以通过检查 scause 的值来确定是否调用了 ecall 指令
    if (scause == SCAUSE_ECALL) {
        handle_syscall(f);
        //user_pc += 4, 这是因为 sepc 指向导致异常的程序计数器，它指向 ecall 指令。
        //如果我们不改变它，内核会返回到同一个位置，并且 ecall 指令会被重复执行。
        user_pc += 4;
    } else {
        PANIC("unexpected trap scause=%x, stval=%x, sepc=%x\n", scause, stval, user_pc);
    }

    WRITE_CSR(sepc, user_pc);
}



//存入当前的栈上下文信息，从获取下个任务的栈信息弹出
//，从而切换到另个任务的上下文环境，即相当于调用暂停，延迟返回 
__attribute__((naked)) 
void switch_context(uint32_t *prev_sp, uint32_t *next_sp) 
{
__asm__ __volatile__(
    // 先压栈当前需要的寄存器，后存入 prev_sp地址中
    //在 RISC-V 中，s0到s11是被调用者保存的寄存器
    "addi sp, sp, -13 * 4\n" 
    "sw ra,  0  * 4(sp)\n"   // 让 sp[0] = ra
    "sw s0,  1  * 4(sp)\n"   // 让 sp[1] = s0
    "sw s1,  2  * 4(sp)\n"
    "sw s2,  3  * 4(sp)\n"
    "sw s3,  4  * 4(sp)\n"
    "sw s4,  5  * 4(sp)\n"
    "sw s5,  6  * 4(sp)\n"
    "sw s6,  7  * 4(sp)\n"
    "sw s7,  8  * 4(sp)\n"
    "sw s8,  9  * 4(sp)\n"
    "sw s9,  10 * 4(sp)\n"
    "sw s10, 11 * 4(sp)\n"
    "sw s11, 12 * 4(sp)\n"

    // 切换栈指针
    "sw sp, (a0)\n"         // 将当前修改过的 sp存入prev_sp地址中(即拷贝栈信息)
    "lw sp, (a1)\n"         // 让sp = next_sp

    // 从next_sp的内存中恢复保存的寄存器
    "lw ra,  0  * 4(sp)\n"  // 让 ra = sp[0]
    "lw s0,  1  * 4(sp)\n"  // 让 ra = sp[1]
    "lw s1,  2  * 4(sp)\n"
    "lw s2,  3  * 4(sp)\n"
    "lw s3,  4  * 4(sp)\n"
    "lw s4,  5  * 4(sp)\n"
    "lw s5,  6  * 4(sp)\n"
    "lw s6,  7  * 4(sp)\n"
    "lw s7,  8  * 4(sp)\n"
    "lw s8,  9  * 4(sp)\n"
    "lw s9,  10 * 4(sp)\n"
    "lw s10, 11 * 4(sp)\n"
    "lw s11, 12 * 4(sp)\n"
    "addi sp, sp, 13 * 4\n"  // 我们已从栈中弹出13个4字节寄存器
    "ret\n"
    );
}


//创建进程
struct process procs[PROCS_MAX]; // 所有进程控制结构
extern char __kernel_base[];
extern char _binary_shell_bin_start[], _binary_shell_bin_size[];


// ↓ __attribute__((naked)) 非常重要!
//通过设置sepc，实现从高权限模式切换到低权限模式（用户态）的功能
// 不使用硬件中断而是使用轮询，所以设置 SPIE 位并不是必需的
__attribute__((naked)) void user_entry(void) {
    __asm__ __volatile__(
        "csrw sepc, %[sepc]        \n"
        "csrw sstatus, %[sstatus]  \n"
        "sret                      \n"
        :
        : [sepc] "r" (USER_BASE),
          [sstatus] "r" (SSTATUS_SPIE | SSTATUS_SUM) // updated
    );
}


//创建一个独立的栈，记录该进程的pid等信息模板结构process
struct process *create_process(const void *image, size_t image_size) {
    // 查找未使用的进程控制结构
    struct process *proc = NULL;
    int i;
    for (i = 0; i < PROCS_MAX; i++) {
        if (procs[i].state == PROC_UNUSED) {
            proc = &procs[i];
            break;
        }
    }

    if (!proc)
        PANIC("no free process slots");

    // 设置被调用者保存的寄存器。这些寄存器值将在 switch_context 
    // 中的第一次上下文切换时被恢复。
    uint32_t *sp = (uint32_t *) &proc->stack[sizeof(proc->stack)];
    *--sp = 0;                      // s11
    *--sp = 0;                      // s10
    *--sp = 0;                      // s9
    *--sp = 0;                      // s8
    *--sp = 0;                      // s7
    *--sp = 0;                      // s6
    *--sp = 0;                      // s5
    *--sp = 0;                      // s4
    *--sp = 0;                      // s3
    *--sp = 0;                      // s2
    *--sp = 0;                      // s1
    *--sp = 0;                      // s0
    *--sp = (uint32_t) user_entry;  // ra (返回地址)


    // 对当前任务的运行空间做映射，取物理内存中的动态内存1k空间做页表，将整个程序内存空间做二级页表映射
    /*
        一旦开启了页映射机制，所有内存访问——包括内核本身的访问——都必须经过虚拟地址进行
        一般来说，内核会创建一个特殊的虚拟内存区域，用于直接映射物理内存，
        这样即使内核使用虚拟地址进行访问，也能非常方便地操作物理内存
    */
    /* 
     * 映射内核页面，将使用到的所有内存进行内存映射 (__kernel_base : __free_ram_end)
     * 当前内核内存映射被配置为内核的虚拟地址与物理地址匹配（即 vaddr == paddr）。这允许在启用分页后继续运行相同的代码。
     * 确保内核在运行过程中对相应物理内存区域的访问不会因缺乏映射而出错
     */
    uint32_t *page_table = (uint32_t *) alloc_pages(1);
    for (paddr_t paddr = (paddr_t) __kernel_base;
        paddr < (paddr_t) __free_ram_end; paddr += PAGE_SIZE)
        map_page(page_table, paddr, paddr, PAGE_R | PAGE_W | PAGE_X);

    //将 virtio-blk MMIO 区域映射到页表中，以便内核映射后还可以访问 MMIO 寄存器
    map_page(page_table, VIRTIO_BLK_PADDR, VIRTIO_BLK_PADDR, PAGE_R | PAGE_W); // new

    // 映射用户页 (user.ld范围)，整个镜像数据就被复制并映射到用户空间的虚拟地址中
    for (uint32_t off = 0; off < image_size; off += PAGE_SIZE) {
        paddr_t page = alloc_pages(1); //为虚拟地址先分配物理内存

        // 处理要复制的数据小于页面大小的情况。
        size_t remaining = image_size - off;
        size_t copy_size = PAGE_SIZE <= remaining ? PAGE_SIZE : remaining;

        // 填充用户空间的数据到页空间并映射这个页面。
        memcpy((void *) page, image + off, copy_size);
        map_page(page_table, USER_BASE + off, page,
                 PAGE_U | PAGE_R | PAGE_W | PAGE_X);
    }

    // 初始化字段
    proc->pid = i + 1;
    proc->state = PROC_RUNNABLE;
    proc->sp = (uint32_t) sp;
    proc->page_table = page_table;
    return proc;
}


//进程调度
void yield(void) {
    // 搜索可运行的进程
    struct process *next = idle_proc;
    for (int i = 0; i < PROCS_MAX; i++) {
        struct process *proc = &procs[(current_proc->pid + i) % PROCS_MAX]; //约束索引 i（0-PROCS_MAX）

        //proc->pid > 0这里限制，不会运行空闲任务
        if (proc->state == PROC_RUNNABLE && proc->pid > 0) {  //找到下一个可运行的任务栈
            next = proc;
            break;
        }
    }

    // 如果除了当前进程外没有可运行的进程，返回并继续处理
    if (next == current_proc)
        return;

    //当内核启动时，默认禁用分页（未设置 satp 寄存器）。虚拟地址的行为就像它们与物理地址匹配一样。
    __asm__ __volatile__(
        "sfence.vma\n"                   //刷新当前虚拟地址到物理地址的映射缓冲（TLB）
        "csrw satp, %[satp]\n"           //更新地址转换机制，由[satp]值指定
        "sfence.vma\n"                   //再次执行 sfence.vma 指令，确保新页表生效
        "csrw sscratch, %[sscratch]\n"   //将 sscratch 寄存器设置为新任务的栈顶地址, 地址值由[sscratch]指定
        :
        // 不要忘记尾随逗号！
        : [satp] "r" (SATP_SV32 | ((uint32_t) next->page_table / PAGE_SIZE)), //指定任务页表，让任务运行在虚拟内存中
          [sscratch] "r" ((uint32_t) &next->stack[sizeof(next->stack)])      //由于栈指针向低地址延伸，我们将地址设置在sizeof(next->stack)字节处作为内核栈的初始值
    );
    
    // 上下文切换
    struct process *prev = current_proc;
    current_proc = next;
    switch_context(&prev->sp, &next->sp);
}

//异常入口函数
/*
异常处理需要切换到内核栈存储信息。
我们不能信任异常发生时的栈指针。在异常处理程序中，我们需要考虑以下三种情况：
1. 在内核模式下发生异常。   --即使我们不重置栈指针通常也没有问题
2. 在处理另一个异常时在内核模式下发生异常（嵌套异常）。 ---我们会覆盖保存区域，但我们的实现在嵌套异常时会触发内核恐慌，所以没关系
3. 在用户模式下发生异常。 ---在这种情况下，sp指向“用户（应用程序）栈区域”。如果我们实现时直接使用（信任）sp，可能会导致使内核崩溃的漏洞(sp可能损坏，存在非法内存访问)。
*/
__attribute__((naked)) //告诉编译器不要自动生成函数前后的栈帧保护代码
__attribute__((aligned(4)))
void kernel_entry(void) {
    __asm__ __volatile__(
        // 将 CSR（控制状态寄存器）sscratch 的值和通用寄存器 sp 的值互换
        // 设任务崩溃，交换后，当前sp指向内核栈，sscratch指向用户栈
        "csrrw sp, sscratch, sp\n"

        /* 压栈主要寄存器 */
        "addi sp, sp, -4 * 31\n"  //分配栈空间
        "sw ra,  4 * 0(sp)\n"     //保存寄存器
        "sw gp,  4 * 1(sp)\n"
        "sw tp,  4 * 2(sp)\n"
        "sw t0,  4 * 3(sp)\n"
        "sw t1,  4 * 4(sp)\n"
        "sw t2,  4 * 5(sp)\n"
        "sw t3,  4 * 6(sp)\n"
        "sw t4,  4 * 7(sp)\n"
        "sw t5,  4 * 8(sp)\n"
        "sw t6,  4 * 9(sp)\n"
        "sw a0,  4 * 10(sp)\n"
        "sw a1,  4 * 11(sp)\n"
        "sw a2,  4 * 12(sp)\n"
        "sw a3,  4 * 13(sp)\n"
        "sw a4,  4 * 14(sp)\n"
        "sw a5,  4 * 15(sp)\n"
        "sw a6,  4 * 16(sp)\n"
        "sw a7,  4 * 17(sp)\n"
        "sw s0,  4 * 18(sp)\n"
        "sw s1,  4 * 19(sp)\n"
        "sw s2,  4 * 20(sp)\n"
        "sw s3,  4 * 21(sp)\n"
        "sw s4,  4 * 22(sp)\n"
        "sw s5,  4 * 23(sp)\n"
        "sw s6,  4 * 24(sp)\n"
        "sw s7,  4 * 25(sp)\n"
        "sw s8,  4 * 26(sp)\n"
        "sw s9,  4 * 27(sp)\n"
        "sw s10, 4 * 28(sp)\n"
        "sw s11, 4 * 29(sp)\n"

        // 在将其他寄存器保存到内核栈之后，我们从sscratch恢复原始的用户sp值到a0并保存到内核栈上
        "csrr a0, sscratch\n"   
        "sw a0, 4 * 30(sp)\n"   //将用户栈栈顶指针值sp，放到最后一个存储空间 4*30中

        // 重置内核栈的栈顶位置，更新 sscratch 为新的栈顶地址，确保异常返回时能正确恢复现场
        "addi a0, sp, 4 * 31\n" 
        "csrw sscratch, a0\n" //在当前 sp 基础上增加 4×31 个字节，作为新内核栈顶

        "mv a0, sp\n"           //a0 = sp(栈底)
        "call handle_trap\n"    //调用具体的异常函数 handle_trap，参数由寄存器提供

        /* 出栈寄存器 */
        "lw ra,  4 * 0(sp)\n"
        "lw gp,  4 * 1(sp)\n"
        "lw tp,  4 * 2(sp)\n"
        "lw t0,  4 * 3(sp)\n"
        "lw t1,  4 * 4(sp)\n"
        "lw t2,  4 * 5(sp)\n"
        "lw t3,  4 * 6(sp)\n"
        "lw t4,  4 * 7(sp)\n"
        "lw t5,  4 * 8(sp)\n"
        "lw t6,  4 * 9(sp)\n"
        "lw a0,  4 * 10(sp)\n"
        "lw a1,  4 * 11(sp)\n"
        "lw a2,  4 * 12(sp)\n"
        "lw a3,  4 * 13(sp)\n"
        "lw a4,  4 * 14(sp)\n"
        "lw a5,  4 * 15(sp)\n"
        "lw a6,  4 * 16(sp)\n"
        "lw a7,  4 * 17(sp)\n"
        "lw s0,  4 * 18(sp)\n"
        "lw s1,  4 * 19(sp)\n"
        "lw s2,  4 * 20(sp)\n"
        "lw s3,  4 * 21(sp)\n"
        "lw s4,  4 * 22(sp)\n"
        "lw s5,  4 * 23(sp)\n"
        "lw s6,  4 * 24(sp)\n"
        "lw s7,  4 * 25(sp)\n"
        "lw s8,  4 * 26(sp)\n"
        "lw s9,  4 * 27(sp)\n"
        "lw s10, 4 * 28(sp)\n"
        "lw s11, 4 * 29(sp)\n"
        "lw sp,  4 * 30(sp)\n"
        "sret\n"                //类似return
    );
}


//程序的入口函数
__attribute__((section(".text.boot"))) //将下面函数放入 .text.boot段中，作为链接的入口函数
__attribute__((naked))
void boot(void) {
    __asm__ __volatile__(
        "mv sp, %[stack_top]\n" // 设置栈指针
        "j kernel_main\n"       // 跳转到内核主函数
        :
        : [stack_top] "r" (__stack_top) // 将栈顶地址作为 %[stack_top] 传递
    );
}



//##################### 硬盘驱动相关 #######################
struct virtio_virtq *blk_request_vq;
struct virtio_blk_req *blk_req;
paddr_t blk_req_paddr;
unsigned blk_capacity;


//访问 MMIO 寄存器与访问普通内存不同。你应该使用 volatile 关键字来防止编译器优化掉读/写操作。
//在 MMIO 中，内存访问可能会触发副作用(例如，向设备发送命令)。
uint32_t virtio_reg_read32(unsigned offset) {
    return *((volatile uint32_t *) (VIRTIO_BLK_PADDR + offset));
}

uint64_t virtio_reg_read64(unsigned offset) {
    return *((volatile uint64_t *) (VIRTIO_BLK_PADDR + offset));
}

void virtio_reg_write32(unsigned offset, uint32_t value) {
    *((volatile uint32_t *) (VIRTIO_BLK_PADDR + offset)) = value;
}

void virtio_reg_fetch_and_or32(unsigned offset, uint32_t value) {
    virtio_reg_write32(offset, virtio_reg_read32(offset) | value);
}


//初始化 virtq，它是 virtio 设备通信的核心数据结构之一
struct virtio_virtq *virtq_init(unsigned index) {
    // 为 virtqueue 分配一个区域。
    paddr_t virtq_paddr = alloc_pages(align_up(sizeof(struct virtio_virtq), PAGE_SIZE) / PAGE_SIZE);
    struct virtio_virtq *vq = (struct virtio_virtq *) virtq_paddr;
    vq->queue_index = index;
    vq->used_index = (volatile uint16_t *) &vq->used.index;
    // 1. 通过写入队列的索引(第一个队列为 0)到 QueueSel 来选择队列。
    virtio_reg_write32(VIRTIO_REG_QUEUE_SEL, index);
    // 5. 通过写入大小到 QueueNum 来通知设备队列大小。
    virtio_reg_write32(VIRTIO_REG_QUEUE_NUM, VIRTQ_ENTRY_NUM);
    // 6. 通过写入其字节值到 QueueAlign 来通知设备已使用的对齐方式。
    virtio_reg_write32(VIRTIO_REG_QUEUE_ALIGN, 0);
    // 7. 将队列第一页的物理编号写入 QueuePFN 寄存器。
    virtio_reg_write32(VIRTIO_REG_QUEUE_PFN, virtq_paddr);
    return vq;
}

//初始化 virtio，环形缓冲区，由 virtio 驱动程序和设备共同使用，用于在虚拟设备（如虚拟磁盘、网络设备等）和驱动程序之间传递数据
//设备驱动程序只是操作系统和设备之间的“粘合剂”
void virtio_blk_init(void) {
    if (virtio_reg_read32(VIRTIO_REG_MAGIC) != 0x74726976)
        PANIC("virtio: invalid magic value");
    if (virtio_reg_read32(VIRTIO_REG_VERSION) != 1)
        PANIC("virtio: invalid version");
    if (virtio_reg_read32(VIRTIO_REG_DEVICE_ID) != VIRTIO_DEVICE_BLK)
        PANIC("virtio: invalid device id");

    // 1. 重置设备。
    virtio_reg_write32(VIRTIO_REG_DEVICE_STATUS, 0);
    // 2. 设置 ACKNOWLEDGE 状态位：表示客户操作系统已注意到该设备。
    virtio_reg_fetch_and_or32(VIRTIO_REG_DEVICE_STATUS, VIRTIO_STATUS_ACK);
    // 3. 设置 DRIVER 状态位。
    virtio_reg_fetch_and_or32(VIRTIO_REG_DEVICE_STATUS, VIRTIO_STATUS_DRIVER);
    // 5. 设置 FEATURES_OK 状态位。
    virtio_reg_fetch_and_or32(VIRTIO_REG_DEVICE_STATUS, VIRTIO_STATUS_FEAT_OK);
    // 7. 执行设备特定的设置，包括发现设备的 virtqueues
    blk_request_vq = virtq_init(0);
    // 8. 设置 DRIVER_OK 状态位。
    virtio_reg_write32(VIRTIO_REG_DEVICE_STATUS, VIRTIO_STATUS_DRIVER_OK);

    // 获取磁盘容量。
    blk_capacity = virtio_reg_read64(VIRTIO_REG_DEVICE_CONFIG + 0) * SECTOR_SIZE;
    printf("virtio-blk: capacity is %d bytes\n", blk_capacity);

    // 分配一个区域来存储对设备的请求。
    blk_req_paddr = alloc_pages(align_up(sizeof(*blk_req), PAGE_SIZE) / PAGE_SIZE);
    blk_req = (struct virtio_blk_req *) blk_req_paddr;
}




// 通知设备有新的请求。`desc_index` 是新请求头描述符的索引。
void virtq_kick(struct virtio_virtq *vq, int desc_index) {
    vq->avail.ring[vq->avail.index % VIRTQ_ENTRY_NUM] = desc_index;
    vq->avail.index++;
    __sync_synchronize();
    virtio_reg_write32(VIRTIO_REG_QUEUE_NOTIFY, vq->queue_index);
    vq->last_used_index++;
}

// 返回是否有请求正在被设备处理。
bool virtq_is_busy(struct virtio_virtq *vq) {
    return vq->last_used_index != *vq->used_index;
}

// 从 virtio-blk 设备读取/写入。
void read_write_disk(void *buf, unsigned sector, int is_write) {
    if (sector >= blk_capacity / SECTOR_SIZE) {
        printf("virtio: tried to read/write sector=%d, but capacity is %d\n",
              sector, blk_capacity / SECTOR_SIZE);
        return;
    }

    // 根据 virtio-blk 规范构造请求。
    blk_req->sector = sector;
    blk_req->type = is_write ? VIRTIO_BLK_T_OUT : VIRTIO_BLK_T_IN;
    if (is_write)
        memcpy(blk_req->data, buf, SECTOR_SIZE);

    // 构造 virtqueue 描述符(使用 3 个描述符)。
    struct virtio_virtq *vq = blk_request_vq;
    vq->descs[0].addr = blk_req_paddr;
    vq->descs[0].len = sizeof(uint32_t) * 2 + sizeof(uint64_t);
    vq->descs[0].flags = VIRTQ_DESC_F_NEXT;
    vq->descs[0].next = 1;

    vq->descs[1].addr = blk_req_paddr + offsetof(struct virtio_blk_req, data);
    vq->descs[1].len = SECTOR_SIZE;
    vq->descs[1].flags = VIRTQ_DESC_F_NEXT | (is_write ? 0 : VIRTQ_DESC_F_WRITE);
    vq->descs[1].next = 2;

    vq->descs[2].addr = blk_req_paddr + offsetof(struct virtio_blk_req, status);
    vq->descs[2].len = sizeof(uint8_t);
    vq->descs[2].flags = VIRTQ_DESC_F_WRITE;

    // 通知设备有新的请求。
    virtq_kick(vq, 0);

    // 等待设备完成处理。
    while (virtq_is_busy(vq))
        ;

    // virtio-blk：如果返回非零值，则表示错误。
    if (blk_req->status != 0) {
        printf("virtio: warn: failed to read/write sector=%d status=%d\n",
               sector, blk_req->status);
        return;
    }

    // 对于读操作，将数据复制到缓冲区。
    if (!is_write)
        memcpy(buf, blk_req->data, SECTOR_SIZE);
}


//用户的主函数
void kernel_main(void) {
    memset(__bss, 0, (size_t) __bss_end - (size_t) __bss);

    printf("kernel_main");
    WRITE_CSR(stvec, (uint32_t) kernel_entry); // 设置stvec，即设置内核异常处理的入口

    virtio_blk_init(); // 硬盘驱动初始化

    fs_init(); //文件系统初始化

    //创建空闲进程
    idle_proc = create_process(NULL, 0);
    idle_proc->pid = 0; // idle
    current_proc = idle_proc;

    //创建客户进程
    create_process(_binary_shell_bin_start, (size_t) _binary_shell_bin_size);

    //第一次切换，直接切到用户空间去（这里用主动切换，不会轮询）
    yield(); 
    PANIC("switched to idle process");

    for (;;) {
        __asm__ __volatile__("wfi"); //让设备进入低功耗状态
    }
}


//##################### 文件系统相关 #######################
struct file files[FILES_MAX];
uint8_t disk[DISK_MAX_SIZE];

int oct2int(char *oct, int len) {
    int dec = 0;
    for (int i = 0; i < len; i++) {
        if (oct[i] < '0' || oct[i] > '7')
            break;

        dec = dec * 8 + (oct[i] - '0');
    }
    return dec;
}

void fs_init(void) {
    //从磁盘读取每个扇区到disk中
    for (unsigned sector = 0; sector < sizeof(disk) / SECTOR_SIZE; sector++)
        read_write_disk(&disk[sector * SECTOR_SIZE], sector, false);

    unsigned off = 0;
    //解析tar中的文件
    for (int i = 0; i < FILES_MAX; i++) {
        struct tar_header *header = (struct tar_header *) &disk[off];
        if (header->name[0] == '\0')
            break;

        if (strcmp(header->magic, "ustar") != 0)
            PANIC("invalid tar header: magic=\"%s\"", header->magic);

        //获取文件的相关信息
        int filesz = oct2int(header->size, sizeof(header->size)); //oct2int 将 header->size 内存中的数据（通常以八进制表示）转换为整数
        struct file *file = &files[i];
        file->in_use = true;
        strcpy(file->name, header->name);
        memcpy(file->data, header->data, filesz);
        file->size = filesz;
        printf("\r\nfile: %s, size=%d\n", file->name, file->size);

        //更新下一个 tar 文件头位置 
        off += align_up(sizeof(struct tar_header) + filesz, SECTOR_SIZE);
    }
}


void fs_flush(void) {
    // 将所有文件内容复制到 `disk` 缓冲区
    memset(disk, 0, sizeof(disk));
    unsigned off = 0;
    for (int file_i = 0; file_i < FILES_MAX; file_i++) {
        struct file *file = &files[file_i];
        if (!file->in_use)
            continue;

        struct tar_header *header = (struct tar_header *) &disk[off];
        memset(header, 0, sizeof(*header));
        strcpy(header->name, file->name);
        strcpy(header->mode, "000644");
        strcpy(header->magic, "ustar");
        strcpy(header->version, "00");
        header->type = '0';

        // 将文件大小转换为八进制字符串
        int filesz = file->size;
        for (int i = sizeof(header->size); i > 0; i--) {
            header->size[i - 1] = (filesz % 8) + '0';
            filesz /= 8;
        }

        // 计算校验和
        int checksum = ' ' * sizeof(header->checksum);
        for (unsigned i = 0; i < sizeof(struct tar_header); i++)
            checksum += (unsigned char) disk[off + i];

        for (int i = 5; i >= 0; i--) {
            header->checksum[i] = (checksum % 8) + '0';
            checksum /= 8;
        }

        // 复制文件数据
        memcpy(header->data, file->data, file->size);
        off += align_up(sizeof(struct tar_header) + file->size, SECTOR_SIZE);
    }

    // 将 `disk` 缓冲区写入 virtio-blk
    for (unsigned sector = 0; sector < sizeof(disk) / SECTOR_SIZE; sector++)
        read_write_disk(&disk[sector * SECTOR_SIZE], sector, true);

    printf("wrote %d bytes to disk\n", sizeof(disk));
}


struct file *fs_lookup(const char *filename) {
    for (int i = 0; i < FILES_MAX; i++) {
        struct file *file = &files[i];
        if (!strcmp(file->name, filename))
            return file;
    }

    return NULL;
}